"IW-Half-Dither" is a GX printer driver designed to allow an ImageWriter II to render and print in halftone using the 'rdip' resource. It is based on the sample driver source found in :
It makes use of the in built rendering abilities of QuickDraw GX.
In addition, it demonstrates :
- How to change the settings generated by the 'rdip' on the fly so that it is possible to switch between dithering and half toning
with one driver.
- How to add and manage multiple panels to a print dialog.
- How to map a gxExtendedDITLType('xdtl') resource to a collection item.
- How to switch color planes on and off.
It's not intended to be a definitive 'document' on how to implement these features, but illustrates one way of doing it.
How to use the driver
Installation
Make sure QuickDraw GX is installed, drag the "IW-Half-Dither" extension over the system folder. It should automatically be moved into the extensions folder. Restart the Macintosh.
Go to the Chooser and select the "IW-Half-Dither". Create a desktop printer appropriate to how your ImageWriter is connected to your computer.
When you next print from a GX aware application, the print dialog should now have two new panels: separations and rendering.
General
"IW-Half-Dither" works like the standard ImageWriter driver except that it defaults to half tone rendering instead of dither and it has two extra panels in the print dialog. These two panels allow the user more control over how the final image appears from the ImageWriter
Separations Panel
This panel contains 4 check boxes - one for each CMYK plane. Clicking them on or off determines which planes the printer will print.There is more information later in the Read Me that describes how this is done.
Rendering Panel
This panel controls how the image is rendered. There are two modes - dither and halftone. These are set by clicking in the appropriate radio button. In dither mode, there is only one option "Dither Level". This is a popup menu which gives a range of 1-16. The effect of changing this value is described in "Inside Macintosh : Quickdraw GX - Objects" Pages 7-11 to 7-12. In half tone mode, you can change the "Dot shape" globally and the angle and frequency for each color. The angle takes a floating point number in degrees and the frequency refers to dots per inch.
Building
MPW build notes
This printer driver builds using the latest release of the MPW environment (currently 20), using the below command line when the current directory is set to that of the printer driver.
BuildProgram -e IW-Half-Dither
Please note that before building the printer driver the following steps have to be taken:
* Insert the Mac OS SDK containing the QuickDraw GX SDK, (currently the Dev CD July 96 MacOS SDK2).
* Copy the GXLibraries folder from the 'Development Kits (Disc2):QuickDraw™GX:Programming Stuff:' folder and place it into the :MPW:Libraries: folder.
CodeWarrior build notes
To build the printer driver the following steps have to be taken:
* First, build the IWriterChooserPACK.µ project as this contains all of the driver resource .r files for the projects and also creates the target file if not present.
* Second, build the ImageWriterGX68k.µ, ImageWriterGXNew68k.µ and IWriterChooserLDEF.µ projects in any order. These are automatically merged into the target file during linking.
Please note that the project files to build the printer driver are for CodeWarrior 8 and whilst the code should compile in earlier versions, projects aren't supplied as the Rez compiler plug-in is necessary and is supplied starting with CodeWarrior release 8.
How the interesting stuff works
Modifications to the gxRasterPrefsType('rdip') resource for half toning
The gxRasterPrefsType or 'rdip' resource can be found in NewApp.r. In its original format it is setup for dithering. This default already produces a good quality of output on the ImageWriter II; however, this sample is meant to demonstrate how to add half toning. The top part of the 'rdip' does not need any alteration and can remain the same for both half-toning and dithering:
These are general setup instructions for the renderer and don't need to be altered. The changes come in the next section - the plane array. As each plane is changed in roughly the same way, I'll only describe the first.
gxDefaultOffscreen, // YELLOW plane. Use default half-toning.
0x000F0000, // Angle = 15 degrees
0x002D0000, // Freq = 45
gxRoundDot, // RoundDot
gxComponent3Tint, // Extract yellow and dither it
gxRGBSpace, gxNoProfile, 0, 0, 0, 0, // DotColor == black
gxRGBSpace, gxNoProfile, 0xFFFF, 0xFFFF, 0xFFFF, 0, // Background color == white
gxCMYKSpace, // Convert to gxCMYKSpace before half-toning.
gxNoSpace, // No explicit color space
gxNoSet, // No color set
gxNoProfile, // No profile specified
Plane flags = gxDefaultOffscreen - This will make the render use the default halftoning.
Angle = 0x000F0000 - This is a fixed point value which refers to the angle in degrees used for
halftoning. This should be varied for each plane. In this case it is an angle
of 15 degrees
Frequency = 0x002D0000 - This is the number of cells per inch. In this case 45 cells per inch. It is a
fixed point value.
Dither type = gxRoundDot - This is the shape of the dot. The render can produce dots of different
shapes which produce different effects.
Tint Type = gxComponent3Tint - This is the tinting that we are going to apply to this plane.
The constants for CMYK half-toning are:
Cyan gxComponent1Tint
Magenta gxComponent2Tint
Yellow gxComponent3Tint
Black gxComponent4Tint
As we print yellow first, the first planes tint type is gxComponent3Tint.
Dot Color and Background Color - These are the dot colors that are used in this plane. They are set to black
dot and white background respectively.
Tint Space = gxCMYKSpace - This will cause the render to convert the colors to CMYK space before
rendering.
Plane Color Space = gxNoSpace - This is the color space of the original plane. We don't need to set it.
Plane Color Set = gxNoSet - This is the resource ID of a color set to use for the plane. We don't need
to set this.
Plane Color Profile = gxNoProfile- This is the resource ID of a color profile to use for the plane. In this case,
we don't supply one.
The other planes are created in the same way except they have different angles and the appropriate tint type. The other difference from the ImageWriter sample driver this was based on, is that you no longer need gxColorSetResType('crst') resource.
Changing the rendering settings on the fly
It is not possible to change how a GX driver renders by simply patching the 'rdip'. However, there is a way of doing this by overriding the universal image message, GXSetupImageData. This message allows you to alter the initialization information about a printer; therefore, the type of data passed to it can vary depending on what type of driver it is. Also it is worth noting that when it is called, the information has not necessarily been read in. Therefore the first thing your override must do is to forward the message so that the default values are read in. In the case of a raster driver, it is passed a gxRasterImageDataHdl. The routine in the sample is called SD_SetupImageData().
An override for a raster printer is passed a gxRasterImageDataHdl. This contains information about how the planes are setup in a very similar format to the 'rdip'. As rendering has not happened at this stage, it is possible to make changes to this information and therefore allow the user to have a great deal of control over the rendering.
The override in "IW-Half-Dither" does all the normal things the previous driver did; in other words do any required setting up for rough and text modes, whether accurate bit alignment is setup and if the user has put a black and white ribbon in the printer. Having done this, the function then makes changes to the theSetup.planeSetup[] array as appropriate. The information that it puts in here comes from a custom collection kCMYKRenderCollectionType('Dipy'). If the collection doesn't exist, then it does no further processing and uses the information from the 'rdip' resource. The 'Dipy' collection is just there as an example and you should create and use your own. It modifies and sets up the fields in the planeSetup[] array so that it can either produce a halftone or a dither render; because of this it must clear fields that are used by one and not the other. In the case of dither it must also create a color set to be put into the planeSet field.
Switching color planes on and off
The easiest way of achieving this is to make changes to GXRasterPackageBitmap message to only allow the printer to print the colors you want. This is done in "IW-Half-Dither" by the routine
OSErr SD_PackageBitmap (
gxRasterPackageBitmapRec *pPackage,
Ptr buffer, // data goes here + bufferPos
unsigned long *bufferPos, // how much of the buffer already is full
gxRasterImageDataHdl hImageData )
This function is called once per printer head pass. As the ImageWriter interlaces its head pattern, it has to make two head passes per color band. This causes the head to print a color, move the paper down a small amount, do the second pass, then move the paper back ready for the next color. This causes the paper to do a certain amount of "dancing". To avoid this the printer stores up line feeds for blank lines. These are put in the buffer then removed if no printing occurred and stored up. After sending the line feeds, it checks to see if it is really making four passes and whether it should be printing this band:
if ( ( (*hImageData)->packagingInfo.colorPasses == 4)
&& (! PrintThisBand ( pPackage->colorBand )) )
bandIsDirty = false;
else
{
// Work out if the band is dirty - has pixels that need printing
}
PrintThisBand uses the 'Dipy' collection to see if the current band needs to be printed. The collection contains a set of booleans -one for each plane- that control if the plane should be printed. After either checking to see if the band is dirty, it the either prints it or strips out the entered line feeds before returning.
The kCMYKRenderCollectionType('Dipy') collection
struct CMYKRenderCollection
{
// Plane flags, set to true to print the plane
unsigned char cyanIsOn;
unsigned char magentaIsOn;
unsigned char yellowIsOn;
unsigned char blackIsOn;
// Set to "kDitherIt" for dithered output, or "kHalfToneIt" for half tone
unsigned char renderMode;
unsigned char fill1;
// used when in dither mode to select the level of dither
short ditherLevel;
// used when in half tone mode to select how to half tone
This can be used in conjunction with the overrides described above to give the user control of the rendering. The byte alignment is such that it can be accessed by a gxExtendedDITLType('xdtl') resource. The plane flags are used to turn planes on or off.
cyanIsOn, magentaIsOn,
yellowIsOn, blackIsOn - Turns planes on or off
renderMode - Takes one of two constants to decide whether it should dither or half tone:
#define kDitherIt 0
#define kHalfToneIt 1
ditherLevel - This is used in dither mode.
dotType - This is used during half-toning to control the type of dot.
angles[],frequency[] - These are used in half-toning to control the rendering. They take the following
constants :
#define kRenderOptsCyan 0
#define kRenderOptsMagenta 1
#define kRenderOptsYellow 2
#define kRenderOptsBlack 3
Adding multiple panels to the print dialog
To add panels to the dialog you need to several things. First you must create a DITL . The (0,0) origin for it is considered to be the top left of the panel. You should also create a icon family. These will be used in the list on the left of the panel. To connect these together you need to create a gxPrintPanelType('ppnl') resource. This resource is used to connect the DITL and the icons together. It also contains the name of the panel which is displayed underneath the icon.
SetUpPrintPanel() checks to see if the render options collection exists. If it doesn't then it creates one and adds it to the jobs collection. Next the panels are added.
As two panels are added, the above block of code is repeated twice with a different panelResID.
If every active item in your panel is mapped to a collection by a 'xdtl' resource, then you need to do nothing more. However in the "Render" panel, clicking the dither or the halftone button causes items to gray out. Smart though the 'xdtl' is. You can't get it handle this and you must do a little more work. Again you must override a message; this time the GXHandlePanelEvent is overridden by SD_HandlePanelEvent. This function is called and passed gxPanelInfoRecord for every panel that the driver is installed. Therefore it needs to check the panelResId parameter in the passed gxPanelInfoRecord too check which one of the two panels is being sent events. If the event passed in panelEvt was a gxPanelHitEvt, then you can find out which item was clicked by subtracting itemCount from itemHit. This will give you the item number in the DITL. For example
Mapping a gxExtendedDITLType('xdtl') resource to a collection item
gxExtendedDITLType('xdtl') resource allows you to map a collection directly to your DITL in a print panel, however it requires that certain things are true about the alignment of the collection. "IW-Half-Dither" uses four types which will be described below.
Booleans
In the collection these should be "unsigned chars" and are set to 0 or 1 for on or of. They can be byte aligned. In the resource file you need to add a line such as:
3rd Parameter - The byte offset into the collection to an unsigned chars
4th Parameter - The DITL item number
RadioButtons
There should only be one parameter( an "unsigned chars") in the collection and this takes values from 0 to the last button-1. It should be word aligned. In the resource file you need to add a line such as:
3rd Parameter - The byte offset into the collection to an unsigned chars
4th Parameter, onwards - The DITL item numbers of the radio buttons
Pop-Up Menus
There should only be a short in the collection that stores the current values of the pop-up menu. It should be word aligned. In the resource file you need to add a line such as:
3rd Parameter - The byte offset into the collection to a short
4th Parameter - The DITL item number of a control.
Fixed point string
This is used to enter map a Fixed value in the collection to a text edit. It should be word aligned. In the resource file you need to add a line such as: